package ru.amse.tsyganov.jumleditor.view.representation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;

import ru.amse.tsyganov.jumleditor.graphicaleditor.Coordinates;
import ru.amse.tsyganov.jumleditor.model.ActivityGraph;
import ru.amse.tsyganov.jumleditor.model.Decision;
import ru.amse.tsyganov.jumleditor.model.Transition;
import ru.amse.tsyganov.jumleditor.properties.BooleanProperty;
import ru.amse.tsyganov.jumleditor.properties.StringProperty;
import ru.amse.tsyganov.jumleditor.validator.Problem;
import ru.amse.tsyganov.jumleditor.validator.Validator;
import ru.amse.tsyganov.jumleditor.view.ConnectionPoint;
import ru.amse.tsyganov.jumleditor.view.SelectedItems;
import ru.amse.tsyganov.jumleditor.view.Validity;
import ru.amse.tsyganov.jumleditor.view.ViewVisitor;
import ru.amse.tsyganov.jumleditor.view.activepoints.ActivePoint;
import ru.amse.tsyganov.jumleditor.view.activepoints.LineEditingActivePoint;
import ru.amse.tsyganov.jumleditor.view.activepoints.TransitionActivePoint;
import ru.amse.tsyganov.jumleditor.view.lines.Line;
import ru.amse.tsyganov.jumleditor.view.lines.LineBuilder;
import ru.amse.tsyganov.jumleditor.view.lines.LinePainter;
import ru.amse.tsyganov.jumleditor.view.preferences.Colors;
import ru.amse.tsyganov.jumleditor.view.preferences.Images;

public class TransitionView<T extends Transition> extends ViewElement<Transition> {
	
	private final ActivityGraphView<? extends ActivityGraph> activityGraphView;
	
	private final List<ActivePoint> points;
	
	private StringProperty guardExpressionProperty;
	
	private BooleanProperty guardProperty;
	
	private Point guardExpressionExtent;
	
	private ConnectionPoint sourceConnectionPoint;
	
	private ConnectionPoint targetConnectionPoint;
	
	private Line[] brokenLine;
	
	public TransitionView(T model,
			ActivityGraphView<? extends ActivityGraph> activityGraphView,
			ConnectionPoint sourceConnectionPoint,
			ConnectionPoint targetConnectionPoint) {
		
		super(model);
		
		if (activityGraphView == null || sourceConnectionPoint == null || 
				targetConnectionPoint == null) {
			
			throw new IllegalArgumentException();
		}
		
		points = createActivePoints();
		this.activityGraphView = activityGraphView;
		this.sourceConnectionPoint = sourceConnectionPoint;
		this.targetConnectionPoint = targetConnectionPoint;
		initProperties();
		initBrokenLine();
	}
	
	@Override
	public void paint(GC gc, Coordinates coordinates) {
		assert gc != null;
		
		gc.setForeground(getValidity().getColor());
		gc.setBackground(getValidity().getColor());
		
		LinePainter.drawArrowedLine(gc, coordinates, brokenLine);
				
		drawGuard(gc, coordinates);
	}

	private void drawGuard(GC gc, Coordinates coordinates) {
		if (getModel().isGuard()) {
			gc.setBackground(Colors.getWhiteColor());
			
			String expression = "[" + getModel().getGuardExpression() + "]";
			
			guardExpressionExtent = gc.stringExtent(expression);
			
			gc.drawString(expression, 
					coordinates.fromModelXToScreenX(
							getGuardOutputPoint().x - guardExpressionExtent.x / 2), 
					coordinates.fromModelYToScreenY(
							getGuardOutputPoint().y - guardExpressionExtent.y / 2));
		}
	}

	@Override
	public void accept(ViewVisitor v) {
		v.visitTransitionView(this);
	}
	
	@Override
	public List<? extends ActivePoint> getActivePoints() {
		if (getSelectedItems().selectedTransitionsContains(this)) {
			return points;
		} else {
			return Collections.emptyList();
		}
	}
	
	@Override
	public ActivityGraphView<? extends ActivityGraph> getActivityGraphView() {
		return activityGraphView;
	}
	
	public Point getStartPoint() {
		return new Point(sourceConnectionPoint.getCpX(),
				sourceConnectionPoint.getCpY());
	}
	
	public Point getEndPoint() {
		return new Point(targetConnectionPoint.getCpX(),
				targetConnectionPoint.getCpY());
	}
	
	public Point getGuardOutputPoint() {
		return new Point(
				(brokenLine[0].getX1() + brokenLine[0].getX2()) / 2,
				(brokenLine[0].getY1() + brokenLine[0].getY2()) / 2);
	}
	
	private List<ActivePoint> createActivePoints() {
		List<ActivePoint> points = new ArrayList<ActivePoint>(2);
		TransitionActivePoint startPoint = new TransitionActivePoint(true) {
			@Override
			public int getCurrentActivePointX() {
				return getStartPoint().x;
			}
			@Override
			public int getCurrentActivePointY() {
				return getStartPoint().y;
			}
			@Override
			public TransitionView<? extends Transition> getTransitionView() {
				return TransitionView.this;
			}
			@Override
			public ActivityGraphView<? extends ActivityGraph> getContainer() {
				return activityGraphView;
			}
		};
		
		TransitionActivePoint endPoint = new TransitionActivePoint(false) {
			@Override
			public int getCurrentActivePointX() {
				return getEndPoint().x;
			}
			@Override
			public int getCurrentActivePointY() {
				return getEndPoint().y;
			}
			@Override
			public TransitionView<? extends Transition> getTransitionView() {
				return TransitionView.this;
			}
			@Override
			public ActivityGraphView<? extends ActivityGraph> getContainer() {
				return activityGraphView;
			}
		};
		points.add(startPoint);
		points.add(endPoint);
		return points;
	}

	@Override
	public void paintActivePoints(GC gc, Coordinates coordinates) {
		if (getSelectedItems().selectedTransitionsContains(this)) {
			for (ActivePoint p : getActivePoints()) {
				p.paintActivePoint(gc, coordinates);
			}
		}
	}
	
	private void initProperties() {
		
		guardProperty = new BooleanProperty("Guard") {

			@Override
			public Boolean getPropertyValue() {
				return getModel().isGuard();
			}

			@Override
			public void setPropertyValue(Boolean newValue) {
				getModel().setGuard(newValue);
				activityGraphView.getModel().fireModelChanged();
			}
						
		};
		
		super.getProperties().addProperty(guardProperty);
		
		guardExpressionProperty = new StringProperty("Guard expression") {

			@Override
			public String getPropertyValue() {
				return getModel().getGuardExpression();
			}

			@Override
			public void setPropertyValue(String newValue) {
				getModel().setGuardExpression(newValue);
				activityGraphView.getModel().fireModelChanged();
			}
			
			@Override
			public boolean canModify() {
				return getModel().isGuard();
			}
			
		};
		
		super.getProperties().addProperty(guardExpressionProperty);
	}
	
	@Override
	public StringProperty getMainProperty() {
		return guardExpressionProperty;
	}

	public Point getGuardExpressionExtent() {
		return guardExpressionExtent != null ? 
				guardExpressionExtent :
					new Point(10, 10);
	}

	public String getClassName() {
		return "TransitionView";
	}

	@Override
	public List<Problem> getInnerProblems() {
		List<Problem> problems = new ArrayList<Problem>();
		if (getModel().isGuard()) {
			if (!(getModel().getSource().getContainer() 
					instanceof Decision)) {
				
				SelectedItems problemSpace = new SelectedItems();
				problemSpace.selectedTransitionsAdd(this);
				
				String message = Validator.ERROR +  
					": guard is not allowed here";
				
				problems.add(new Problem(message, 
						problemSpace, Images.getErrorImage()));
				
				setValidity(Validity.ERROR);
			} else if (getModel().getGuardExpression().length() == 0) {
				SelectedItems problemSpace = new SelectedItems();
				problemSpace.selectedTransitionsAdd(this);
				
				String message = Validator.WARNING +  
					": guard expression is empty";
				
				problems.add(new Problem(message, 
						problemSpace, Images.getWarningImage()));
				
				setValidity(Validity.WARNING);
			}
		} else {
			if (getModel().getSource().getContainer() 
					instanceof Decision &&
					getModel().getSource().getContainer().
					getStateVertex().outgoingTransitionsSize() > 1) {
				
				SelectedItems problemSpace = new SelectedItems();
				problemSpace.selectedTransitionsAdd(this);
				
				String message = Validator.ERROR +  
					": guard should be here";
				
				problems.add(new Problem(message, 
						problemSpace, Images.getErrorImage()));
				
				setValidity(Validity.ERROR);
			}
		}
		return problems;
	}

	public ConnectionPoint getSourceConnectionPoint() {
		return sourceConnectionPoint;
	}

	public void setSourceConnectionPoint(ConnectionPoint sourceConnectionPoint) {
		if (sourceConnectionPoint == null) {
			throw new IllegalArgumentException();
		}
		this.sourceConnectionPoint = sourceConnectionPoint;
		initBrokenLine();
	}

	public ConnectionPoint getTargetConnectionPoint() {
		return targetConnectionPoint;
	}

	public void setTargetConnectionPoint(ConnectionPoint targetConnectionPoint) {
		if (targetConnectionPoint == null) {
			throw new IllegalArgumentException();
		}
		this.targetConnectionPoint = targetConnectionPoint;
		initBrokenLine();
	}
	
	public boolean contains(int x, int y) {
		if (brokenLine == null) {
			return false;
		}
		final int D = 10; 
		for (Line l : brokenLine) {
			int minX = Math.min(l.getX1(), l.getX2()) - D;
			int minY = Math.min(l.getY1(), l.getY2()) - D;
			int maxX = Math.max(l.getX1(), l.getX2()) + D;
			int maxY = Math.max(l.getY1(), l.getY2()) + D;
			if (new Rectangle(minX, minY, maxX - minX, maxY - minY).contains(x, y)) {
				return true;
			}
		}
		return false;
	}
	
	public void initBrokenLine() {
		for (Iterator<ActivePoint> i = points.iterator(); i.hasNext(); ) {
			ActivePoint p = i.next();
			if (p instanceof LineEditingActivePoint) {
				i.remove();
			}
		}

		brokenLine = LineBuilder.getBrokenLine(sourceConnectionPoint, targetConnectionPoint);
		for (int i = 1; i < brokenLine.length - 1; i++) {
			LineEditingActivePoint p = new LineEditingActivePoint(brokenLine[i]);
			points.add(p);
		}
	}
	
	public void initBrokenLine(int v, int h) {
		for (Iterator<ActivePoint> i = points.iterator(); i.hasNext(); ) {
			ActivePoint p = i.next();
			if (p instanceof LineEditingActivePoint) {
				i.remove();
			}
		}

		brokenLine = LineBuilder.getBrokenLine(sourceConnectionPoint, targetConnectionPoint, v, h);
		for (int i = 1; i < brokenLine.length - 1; i++) {
			LineEditingActivePoint p = new LineEditingActivePoint(brokenLine[i]);
			points.add(p);
		}
	}
	
	public int getBrokenLineVerticalSegment() {
		if (brokenLine[0].getY1() == brokenLine[0].getY2()) {
			return brokenLine[0].getX2();
		} else {
			return brokenLine[brokenLine.length - 1].getX1();
		}
	}
	
	public int getBrokenLineHorizontalSegment() {
		if (brokenLine[0].getX1() == brokenLine[0].getX2()) {
			return brokenLine[0].getY2();
		} else {
			return brokenLine[brokenLine.length - 1].getY1();
		}
	}
}
